package com.masonluo.mlonlinejudge.acl.utils;

import org.springframework.lang.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author masonluo
 * @date 2021/5/5 12:19 下午
 */
public class ObjectUtils {
    /**
     * 判断对象是否含有某个注解，包括对象下的方法
     */
    public static boolean hasAnnotationIncludingMethod(Object obj, Class<? extends Annotation> annotation) {
        Class<?> clazz = obj.getClass();
        if (clazz.isAnnotationPresent(annotation)) {
            return true;
        }
        Method[] methods = clazz.getDeclaredMethods();
        if (isEmpty(methods)) {
            return false;
        }
        for (Method method : methods) {
            return hasAnnotationIncludingInner(method, annotation);
        }
        return false;
    }

    /**
     * 获取对象中被某个注解注解到的方法
     */
    public static List<Method> acquireAnnotatedMethod(Object obj, Class<? extends Annotation> annotation) {
        Class<?> clazz = obj.getClass();
        List<Method> container = new ArrayList<>();
        Method[] methods = clazz.getDeclaredMethods();
        if (isEmpty(methods)) {
            return container;
        }
        for (Method method : methods) {
            if (method.isAnnotationPresent(annotation)) {
                container.add(method);
            }
        }
        return container;
    }


    private static boolean hasAnnotationIncludingInner(Method method, Class<? extends Annotation> annotation) {
        Annotation[] annotated = method.getAnnotations();
        if (isEmpty(annotated)) {
            return false;
        }
        for (Annotation anno : annotated) {
            if (nullSafeEquals(anno.annotationType().getName(), annotation.getName())) {
                return true;
            }
            if (isAnnotationPresent(anno, annotation)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param anno
     * @param annotation
     * @return
     */
    private static boolean isAnnotationPresent(Annotation anno, Class<? extends Annotation> annotation) {
        Class<?> annoClass = anno.annotationType();
        return annoClass.isAnnotationPresent(annotation);
    }

    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        }
        if (obj instanceof Optional) {
            return !((Optional<?>) obj).isPresent();
        }
        if (obj instanceof CharSequence) {
            return ((CharSequence) obj).length() == 0;
        }
        if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        }
        if (obj instanceof Collection) {
            return ((Collection<?>) obj).isEmpty();
        }
        if (obj instanceof Map) {
            return ((Map<?, ?>) obj).isEmpty();
        }
        // else
        return false;
    }

    public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
        if (o1 == o2) {
            return true;
        }
        if (o1 == null || o2 == null) {
            return false;
        }
        if (o1.equals(o2)) {
            return true;
        }
        if (o1.getClass().isArray() && o2.getClass().isArray()) {
            return arrayEquals(o1, o2);
        }
        return false;
    }

    private static boolean arrayEquals(Object o1, Object o2) {
        if (o1 instanceof Object[] && o2 instanceof Object[]) {
            return Arrays.equals((Object[]) o1, (Object[]) o2);
        }
        if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
        }
        if (o1 instanceof byte[] && o2 instanceof byte[]) {
            return Arrays.equals((byte[]) o1, (byte[]) o2);
        }
        if (o1 instanceof char[] && o2 instanceof char[]) {
            return Arrays.equals((char[]) o1, (char[]) o2);
        }
        if (o1 instanceof double[] && o2 instanceof double[]) {
            return Arrays.equals((double[]) o1, (double[]) o2);
        }
        if (o1 instanceof float[] && o2 instanceof float[]) {
            return Arrays.equals((float[]) o1, (float[]) o2);
        }
        if (o1 instanceof int[] && o2 instanceof int[]) {
            return Arrays.equals((int[]) o1, (int[]) o2);
        }
        if (o1 instanceof long[] && o2 instanceof long[]) {
            return Arrays.equals((long[]) o1, (long[]) o2);
        }
        if (o1 instanceof short[] && o2 instanceof short[]) {
            return Arrays.equals((short[]) o1, (short[]) o2);
        }
        return false;
    }

    public Annotation getAnnotation(Method method, Class<? extends Annotation> anno) {
        return method.getAnnotation(anno);
    }
}
